﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Web.Script.Serialization;
using System.Collections.Specialized;

namespace Web.Enhancements.Rest
{
    /// <summary>
    /// Class provides the functionality for reading and conversion stream data (HTTP message body) into a parameter.
    /// </summary>
    internal class HttpBodyParameterReader
    {
        public NameValueCollection Collection {get;set;}
        public HttpBodyParameterContext ParameterContext { get; set; }

        /// <summary>
        /// Default constructor.
        /// </summary>
        public HttpBodyParameterReader(NameValueCollection collection, HttpBodyParameterContext ppContext)
        {
            this.Collection = collection;
            this.ParameterContext = ppContext;
        }

        /// <summary>
        /// Reads data from the stream and converts it into an object to be passed into a parameter.
        /// </summary>
        /// <param name="stream">Input stream from request.</param>
        /// <param name="contentType">The content formatting type.</param>
        /// <param name="conversionHint">Conversion hint from configuration attaribute.</param>
        /// <param name="convertToType">The type to convert to.</param>
        /// <returns>Returns an object.</returns>
        public object Read(Type convertToType)
        {
            string contentType = this.ParameterContext.ContentType.ToLower();
            Stream stream = this.ParameterContext.InputStream;
            switch (this.ParameterContext.ConversionHint)
            {
                case ParameterConversionHint.Binary:
                    if (convertToType == typeof(System.Byte[]))
                        return this.ConvertToBinary(stream);
                    else
                        throw new InvalidOperationException(string.Format("Parameter data cannot be converted from {0} to binary as instructed by hint.", convertToType.FullName));
                case ParameterConversionHint.Text:
                    return this.ConvertToString(stream);
                case ParameterConversionHint.Xml:
                    return this.DeserializeFromXml(stream, convertToType);
                case ParameterConversionHint.Json:
                    return this.DeserializeFromJson(this.ConvertToString(stream), convertToType);
                case ParameterConversionHint.JsonContract:
                    return this.DeserializeDataContract(this.ConvertToString(stream), convertToType);
                case ParameterConversionHint.XmlOrJson:
                    if (contentType.Contains("json"))
                        return this.DeserializeFromJson(this.ConvertToString(stream), convertToType);
                    return this.DeserializeFromXml(stream, convertToType);
                case ParameterConversionHint.JsonOrXml:
                    if (contentType.Contains("xml"))
                        return this.DeserializeFromXml(stream, convertToType);
                    return this.DeserializeFromJson(this.ConvertToString(stream), convertToType);
                case ParameterConversionHint.XmlOrJsonContract:
                    if (contentType.Contains("json"))
                        return this.DeserializeDataContract(this.ConvertToString(stream), convertToType);
                    return this.DeserializeFromXml(stream, convertToType);
                case ParameterConversionHint.JsonContractOrXml:
                    if (contentType.Contains("xml"))
                        return this.DeserializeFromXml(stream, convertToType);
                    return this.DeserializeDataContract(this.ConvertToString(stream), convertToType);
                case ParameterConversionHint.FormOrQueryVars:
                    HtmlFormSerializer ser = new HtmlFormSerializer();
                    return ser.Deserialize(this.Collection, convertToType);
                default:
                    if (convertToType == typeof(System.Byte[]))
                        return this.ConvertToBinary(stream);

                    string data = this.ConvertToString(stream);
                    if (ScalarFormatter.IsTypeSupported(convertToType))
                        return ScalarFormatter.FromString(data, convertToType);

                    if (contentType.Contains("json"))
                        return this.DeserializeDataContract(data, convertToType);

                    if (contentType.Contains("xml"))
                        return this.DeserializeFromXml(stream, convertToType);

                    throw new InvalidOperationException(string.Format("Parameter data cannot be converted to {0}. Providing Parameter Conversion Hint may help to resolve the type correctly.", convertToType.FullName));
            }
        }

        /// <summary>
        /// Converts data from request into an object to be passed into a parameter.
        /// </summary>
        /// <param name="data">The input data from request as a string.</param>
        /// <param name="conversionHint">Conversion hint from configuration attaribute.</param>
        /// <param name="convertToType">The type to convert to.</param>
        /// <returns>Returns an object.</returns>
        public object ReadFromString(string data, Type convertToType)
        {
            string contentType = this.ParameterContext.ContentType.ToLower();
            switch (this.ParameterContext.ConversionHint)
            {
                case ParameterConversionHint.Binary:
                    if (convertToType == typeof(System.Byte[]))
                        return System.Text.Encoding.Default.GetBytes(data);
                    else
                        throw new InvalidOperationException(string.Format("Parameter data cannot be converted from {0} to binary as instructed by hint.", convertToType.FullName));
                case ParameterConversionHint.Text:
                    return data;
                case ParameterConversionHint.Xml:
                    return this.DeserializeFromXml(data, convertToType);
                case ParameterConversionHint.Json:
                    return this.DeserializeFromJson(data, convertToType);
                case ParameterConversionHint.JsonContract:
                    return this.DeserializeDataContract(data, convertToType);
                case ParameterConversionHint.XmlOrJson:
                    if (contentType.Contains("json"))
                        return this.DeserializeFromJson(data, convertToType);
                    return this.DeserializeFromXml(data, convertToType);
                case ParameterConversionHint.JsonOrXml:
                    if (contentType.Contains("xml"))
                        return this.DeserializeFromXml(data, convertToType);
                    return this.DeserializeFromJson(data, convertToType);
                case ParameterConversionHint.XmlOrJsonContract:
                    if (contentType.Contains("json"))
                        return this.DeserializeDataContract(data, convertToType);
                    return this.DeserializeFromXml(data, convertToType);
                case ParameterConversionHint.JsonContractOrXml:
                    if (contentType.Contains("xml"))
                        return this.DeserializeFromXml(data, convertToType);
                    return this.DeserializeDataContract(data, convertToType);
                default:
                    if (convertToType == typeof(System.Byte[]))
                        return System.Text.Encoding.Default.GetBytes(data);

                    if (ScalarFormatter.IsTypeSupported(convertToType))
                        return ScalarFormatter.FromString(data, convertToType);

                    throw new InvalidOperationException(string.Format("Parameter data cannot be converted to {0}. Providing Parameter Conversion Hint may help to resolve the type correctly.", convertToType.FullName));
            }
        }

        private byte[] ConvertToBinary(Stream stream)
        {
            if (stream.Length > int.MaxValue)
                throw new InvalidOperationException("The amount of data exceeds the maximum of 2Gb.");
            System.IO.BinaryReader bRdr = new BinaryReader(stream);
            byte[]data = bRdr.ReadBytes((int)stream.Length);
            return data;
        }

        private string ConvertToString(Stream stream)
        {
            System.IO.StreamReader sRdr = new StreamReader(stream);
            string data = sRdr.ReadToEnd();
            return data;
        }

        private object DeserializeFromJson(string jsonData, Type targetType)
        {
            if (this.ParameterContext.UseCustomJsonDeserialiser)
            {
                return DeserializeUsingCustomJsonDeserializer(jsonData, targetType, false);
            }
            else
            {
                JavaScriptSerializer jsonSerializer = new JavaScriptSerializer();
                return jsonSerializer.Deserialize(jsonData, targetType);
            }
        }

        private object DeserializeDataContract(string jsonData, Type targetType)
        {
            if (this.ParameterContext.UseCustomJsonDeserialiser)
            {
                return DeserializeUsingCustomJsonDeserializer(jsonData, targetType, true);
            }
            else
            {
                MemoryStream mstream = new MemoryStream(Encoding.Default.GetBytes(jsonData));
                System.Runtime.Serialization.Json.DataContractJsonSerializer serializer = new System.Runtime.Serialization.Json.DataContractJsonSerializer(targetType);
                object obj = serializer.ReadObject(mstream);
                mstream.Close();
                mstream.Dispose();
                return obj;
            }
        }

        private static object DeserializeUsingCustomJsonDeserializer(string jsonData, Type targetType, bool hasContract)
        {
            if (WebRestSettings.Current.CustomJsonSerializerProvider == null)
                throw new WebRestException("The use of CustomJsonSerializer is requested, but CustomJsonSerializerProvider is not configured properly.");

            ICustomJsonSerializer jsonDeserializer = WebRestSettings.Current.CustomJsonSerializerProvider.CreateJsonSerializer(targetType);
            return jsonDeserializer.Deserialize(jsonData, targetType, hasContract);
        }

        private object DeserializeFromXml(Stream data, System.Type targetType)
        {
            System.Xml.Serialization.XmlSerializer ser = new System.Xml.Serialization.XmlSerializer(targetType);
            object obj = ser.Deserialize(data);
            return obj;
        }

        private object DeserializeFromXml(string data, System.Type targetType)
        {
            byte[] buffer = System.Text.Encoding.Default.GetBytes(data);
            Stream stream = new MemoryStream(buffer);
            object tmp1 = this.DeserializeFromXml(stream, targetType);
            stream.Close();
            return tmp1;
        }
    }
}
